package com.base.test.mysql2es.model;

import com.base.test.mysql2es.util.CollectionUtil;
import com.base.test.mysql2es.util.ObjectUtil;
import lombok.Getter;
import lombok.Setter;

import java.util.List;
import java.util.Map;

@Getter
@Setter
public class Relation {

    private static final String GT = " > ";
    private static final String EQUALS = " = ";

    /** 数据库表 */
    private String table;

    /** 表中的增量字段名, 通常是 id 或 update_time */
    private String incrementColumn;


    /** 索引名 */
    private String index;

    /** 同步时的 sql 语句 */
    private String sql;

    /** sql 如果是多表时, 上面表中主表的别名 */
    private String tableAlias;

    /** 数据库中的字段存到 es 中时, 是否以驼峰命名 */
    private boolean columnLowerCamel = false;

    /** 写进 es 时的分片字段 */
    private List<String> routeColumn;

    /** 写进 es 时的版本字段, id 或 时间字段, 数据必须要能转换成数字 */
    private String versionColumn;

    /**
     * 如果是同步分表(上面表名上带有 % 时), 是否将匹配数据写进 id,
     * 比如 t_order_% 匹配到表 t_order_202001 和 t_order_202002, 两个表写进 es 的一个 index 时, id 前缀会分别带上 202001 和 202001
     */
    private boolean patternToId = true;

    /** 每次从数据库获取, 以及同步到 es 时的批量条数. 当字段不多时, 增大此值将能提升同步效率 */
    private Integer limit = 1000;

    /** sql 字段和 es 属性的关联 */
    private Map<String, String> mapping;

    /** 跟上面的主表 一对一 的子表 */
    private Map<String, ChildMapping> relationMapping;

    /** 跟上面的主表 多对一 的子表 */
    private Map<String, ChildMapping> nestedMapping;

    /** 写进 es 时的 id 键, 当想要以某个唯一约束值来做为 es 的 _id 时使用 */
    private List<String> keyColumn;

    /** 上面的 sql 中需要忽略写入的字段 */
    private List<String> ignoreColumn;

    /** 表的主键, 用来优化下面的 sql 时有用 */
    private String primaryKey = "id";

    /**
     * sql 中 limit start,xxxx 里的 start 超出这个值就将 sql 优化成 inner join 的方式
     *
     * old: SELECT a,b FROM table LIMIT 1000000,1000
     * new: SELECT a,b FROM table c INNER JOIN (SELECT id FROM table WHERE time > 'xxx' LIMIT 1000000,1000) t on c.id = t.id
     */
    private Integer bigCountToSql = 2000;

    /** 写入 _id 时的前缀 */
    private String idPrefix;
    /** 写入 _id 时的后缀 */
    private String idSuffix;

    void check() {
        ObjectUtil.assertNil(table, "表名(table)必须设置");
        ObjectUtil.assertNil(incrementColumn, "自增列(increment-column)必须设置");

        if (ObjectUtil.isNotBlank(limit)) {
            ObjectUtil.assert0(limit, "limit 必须大于 0");
        }
        if (ObjectUtil.isNotBlank(sql)) {
            sql = ObjectUtil.replaceBlank(sql);
        }
        if (CollectionUtil.isNotEmpty(relationMapping)) {
            for (Map.Entry<String, ChildMapping> entry : relationMapping.entrySet()) {
                String nested = entry.getKey();
                ObjectUtil.assertNil(nested, "一对一键不能为空");
                entry.getValue().check(nested);
            }
        }
        if (CollectionUtil.isNotEmpty(nestedMapping)) {
            for (Map.Entry<String, ChildMapping> entry : nestedMapping.entrySet()) {
                String nested = entry.getKey();
                ObjectUtil.assertNil(nested, "nested 键不能为空");
                entry.getValue().check(nested);
            }
        }
    }

    public String useKey() {
        return String.format("table(%s) <=> index(%s)", table, useIndex());
    }

    /** 如果未设置 type, 从 table name 生成 */
    public String useIndex() {
        if (ObjectUtil.isNotBlank(index)) {
            return index;
        } else if (ObjectUtil.isNotBlank(table)) {
            return ObjectUtil.tableToIndex(table);
        } else {
            return ObjectUtil.EMPTY;
        }
    }

    /** 如果未配置 mapping, 从 column name 生成 */
    public String useField(String column) {
        if (ObjectUtil.isBlank(column) || (CollectionUtil.isNotEmpty(ignoreColumn) && ignoreColumn.contains(column))) {
            return ObjectUtil.EMPTY;
        }

        if (CollectionUtil.isNotEmpty(mapping)) {
            String field = mapping.get(column);
            if (ObjectUtil.isNotBlank(field)) {
                return field;
            }
        }
        return columnLowerCamel ? ObjectUtil.columnToField(column) : column;
    }

    /** 检查表是否需要匹配多张表 */
    public boolean checkMatch() {
        return table.contains("%");
    }
    /** 匹配多张表的 sql 语句 */
    public String matchSql() {
        return String.format("SHOW TABLES LIKE '%s'", table);
    }

    public String matchInfo(String realTable) {
        if (checkMatch() && patternToId) {
            // t_order_%        : t_order_202001        ==>  202001,   t_order_202002        ==>  202002
            // t_order_%_item_% : t_order_2020_item_01  ==>  2020-01,  t_order_2020_item_02  ==>  2020-02
            String tmp = realTable;
            String match = "%";
            String split = "-";
            for (String str : table.split(match)) {
                tmp = tmp.replaceFirst(str, split);
            }
            if (tmp.startsWith(split)) {
                tmp = tmp.substring(1);
            }
            if (tmp.endsWith(split)) {
                tmp = tmp.substring(0, tmp.length() - 1);
            }
            return tmp;
        } else {
            return ObjectUtil.EMPTY;
        }
    }
    /** select ... from ... where (id > xxx | time > '2010-10-10 00:00:01') order by (id | time) limit 1000 */
    public String querySql(String table, String param) {
        // just limit 1000, not limit 1000000, 1000
        return querySql(GT, table, param, 0);
    }

    /** select count(*) from ... where time = '2010-10-10 00:00:01' */
    public String equalsCountSql(String table, String param) {
        return countSql(table, param);
    }
    /**
     * select ... from ... where time = '2010-10-10 00:00:01' limit 0,1000|1000,1000
     * <br><br>or<br><br>
     * select cur.* from ... as cur inner join (select id from ... where time = '2010-01-01 00:00:01' limit 2000,1000) t on t.id = cur.id
     */
    public String equalsQuerySql(String table, String param, int page) {
        int pageStart = page * limit;
        if (pageStart >= bigCountToSql) {
            return bigPageSql(table, param, pageStart);
        } else {
            return querySql(EQUALS, table, param, pageStart);
        }
    }

    public int loopCount(int count) {
        int loop = count / limit;
        if (count % limit != 0) {
            loop += 1;
        }
        return loop;
    }

    private String countSql(String table, String param) {
        String count = "SELECT COUNT(*) FROM ";

        StringBuilder sbd = new StringBuilder();
        if (ObjectUtil.isNotBlank(sql)) {
            // 忽略大小写, 点(.) 匹配所有(包括换行)
            sbd.append(sql.trim().replaceFirst("(?is)SELECT (.*?) FROM ", count));
        } else {
            sbd.append(count).append("`").append(table).append("`");
        }
        appendWhere(EQUALS, param, sbd);
        return sbd.toString();
    }
    private void appendWhere(String operate, String param, StringBuilder sbd) {
        // param split length = increment column size
        String where = " WHERE ";
        boolean containsWhere = sbd.toString().toUpperCase().contains(where);
        if (ObjectUtil.isBlank(param)) {
            param = "0";
        }

        if (containsWhere) {
            sbd.append(" AND ").append("( ");
        } else {
            sbd.append(where);
        }
        if (ObjectUtil.isNotBlank(tableAlias)) {
            sbd.append(tableAlias).append(".");
        }
        sbd.append(incrementColumn).append(operate);
        if (ObjectUtil.isNumber(param)) {
            sbd.append(param);
        } else {
            sbd.append("'").append(param).append("'");
        }

        if (containsWhere) {
            sbd.append(" )");
        }
    }
    private String querySql(String operate, String table, String param, int pageStart) {
        StringBuilder sbd = new StringBuilder();
        if (ObjectUtil.isNotBlank(sql)) {
            sbd.append(sql);
        } else {
            sbd.append("SELECT * FROM `").append(table).append("`");
        }
        appendWhere(operate, param, sbd);
        /*
        if (!EQUALS.equals(operate)) {
            sbd.append(" ORDER BY ");
            if (ObjectUtil.isNotBlank(tableAlias)) {
                sbd.append(tableAlias.trim()).append(".");
            }
            sbd.append(incrementColumn.trim());
        }
        */
        sbd.append(" LIMIT ");
        if (ObjectUtil.greater0(pageStart)) {
            sbd.append(pageStart).append(", ");
        }
        sbd.append(limit);
        return sbd.toString();
    }
    private String bigPageSql(String table, String param, int pageStart) {
        StringBuilder sbd = new StringBuilder();
        if (ObjectUtil.isNotBlank(sql)) {
            sbd.append("SELECT CUR.* FROM (").append(sql).append(")");
        } else {
            sbd.append("SELECT CUR.* FROM `").append(table).append("`");
        }
        sbd.append(" as CUR");
        sbd.append(" INNER JOIN ( SELECT ").append(primaryKey);
        sbd.append(" FROM `").append(table).append("`");
        sbd.append(" WHERE ");
        if (incrementColumn.contains(".")) {
            sbd.append(incrementColumn.substring(incrementColumn.indexOf(".") + 1));
        } else {
            sbd.append(incrementColumn);
        }
        sbd.append(EQUALS);
        if (ObjectUtil.isNumber(param)) {
            sbd.append(param);
        } else {
            sbd.append("'").append(param).append("'");
        }
        sbd.append(" LIMIT ").append(pageStart).append(", ").append(limit);
        sbd.append(" ) TMP on CUR.").append(primaryKey).append(" = TMP.").append(primaryKey);
        return sbd.toString();
    }
}
